#include "rlog.h"
#include "rlog_def.h"

static rlog_t rlog = {0};
static char rlog_buff[RLOG_LINE_BUFF_LEN + 1] = {0};

rlog_assert_hook_t rlog_assert_hook = NULL;
static rlog_output_hook_t rlog_output_hook = NULL;

static const char *level_to_info[RLOG_LVL_MAX] = 
{
    "A/", "E/", "W/", "I/", "D", "V"
};

#if RLOG_COLOUR_ENABLE
static const char *level_to_colour[RLOG_LVL_MAX] = {
    RLOG_COLOUR_ASSERT,
    RLOG_COLOUR_ERROR,
    RLOG_COLOUR_WARN,
    RLOG_COLOUR_INFO,
    RLOG_COLOUR_DEBUG,
    RLOG_COLOUR_VERBOSE,
};
#endif

void rlog_enable(bool enable)
{
    rlog.output_en = enable;
}

void rlog_color_enable(bool enable)
{
    rlog.color_en = enable;
}

void rlog_level_fmt_set(rlog_lvl_t level, int format)
{
    if(level >= RLOG_LVL_MAX)
        return;
    rlog.level_fmt[level] = format;
}

bool rlog_level_fmt_get(rlog_lvl_t level, int format)
{
    if(level >= RLOG_LVL_MAX)
        return false;

    if(rlog.level_fmt[level] & format)
        return true;

    return false;
}

void rlog_level_filter_set(rlog_lvl_t level)
{
    if(level >= RLOG_LVL_MAX)
        return;
    rlog.filter_lvl = level;
}

rlog_lvl_t rlog_level_filter_get()
{
    return (rlog_lvl_t)rlog.filter_lvl;
}

void rlog_assert_set_hook(rlog_assert_hook_t hook)
{
    if(rlog_assert_hook == NULL)
        rlog_assert_hook = hook;
}

void rlog_output_set_hook(rlog_output_hook_t hook)
{
    if(rlog_output_hook == NULL)
        rlog_output_hook = hook;
}

void rlog_print(const char *fmt, ...)
{
    va_list args;
    int log_len = 0;

    if(!rlog.output_en)
        return;

    rlog_lock();
    va_start(args, fmt);

    log_len = rlog_vsnprintf(rlog_buff, RLOG_LINE_BUFF_LEN, fmt, args);
    if((log_len < 0) && (log_len > RLOG_LINE_BUFF_LEN))
        log_len = RLOG_LINE_BUFF_LEN;
    va_end(args);

    rlog_output(rlog_buff, log_len);

    if(rlog_output_hook)
        rlog_output_hook(rlog_buff, log_len);

    rlog_unlock();
}

void rlog_fmt_print(rlog_lvl_t level, const char *tag, const char *file, 
                    const char *func, const int line, const char *fmt, ...)
{
    va_list args;
    int log_len = 0, fmt_len = 0, newline_len = strlen(RLOG_NEWLINE_SIGN);
    char line_num[RLOG_LINE_NUM_SIZE + 1] = {0};

    if(!rlog.output_en || level > rlog.filter_lvl)
        return;

    rlog_lock();
    
#if RLOG_COLOUR_ENABLE
    if(rlog.color_en)
    {
        log_len += rlog_strcpy(log_len, rlog_buff + log_len, RLOG_COLOUR_START);
        log_len += rlog_strcpy(log_len, rlog_buff + log_len, level_to_colour[level]);
    }
#endif /* RLOG_COLOUR_ENABLE */

#if RLOG_TIME_ENABLE
    // add time to log
    if(rlog_level_fmt_get(level, RLOG_FMT_TIME))
    {
        log_len += rlog_strcpy(log_len, rlog_buff + log_len, "(");
        log_len += rlog_strcpy(log_len, rlog_buff + log_len, rlog_get_time());
        log_len += rlog_strcpy(log_len, rlog_buff + log_len, ")");
    }
#endif /* RLOG_TIME_ENABLE */

#if (RLOG_DIRECTORY_ENABLE == 1) || (RLOG_FUNCTION_ENABLE == 1) || (RLOG_LINE_ENABLE == 1)
    // add dir/func/line to log
    if(rlog_level_fmt_get(level, RLOG_FMT_DIR | RLOG_FMT_FUNC | RLOG_FMT_LINE))
    {
        log_len += rlog_strcpy(log_len, rlog_buff + log_len, "[");
#if RLOG_DIRECTORY_ENABLE
        // add dir to log
        if(rlog_level_fmt_get(level, RLOG_FMT_DIR))
        {
            log_len += rlog_strcpy(log_len, rlog_buff + log_len, file);
            if(rlog_level_fmt_get(level, RLOG_FMT_FUNC))
                log_len += rlog_strcpy(log_len, rlog_buff + log_len, " ");
            else if(rlog_level_fmt_get(level, RLOG_FMT_LINE))
                log_len += rlog_strcpy(log_len, rlog_buff + log_len, ":");
        }
#endif /* RLOG_DIRECTORY_ENABLE */

#if RLOG_FUNCTION_ENABLE
        // add func to log
        if(rlog_level_fmt_get(level, RLOG_FMT_FUNC))
        {
            log_len += rlog_strcpy(log_len, rlog_buff + log_len, func);
            if(rlog_level_fmt_get(level, RLOG_FMT_LINE))
                log_len += rlog_strcpy(log_len, rlog_buff + log_len, ":");
        }
#endif /* RLOG_FUNCTION_ENABLE */

#if RLOG_LINE_ENABLE 
        // add line to log
        if(rlog_level_fmt_get(level, RLOG_FMT_LINE))
        {
            rlog_snprintf(line_num, RLOG_LINE_NUM_SIZE, "%d", line);
            log_len += rlog_strcpy(log_len, rlog_buff + log_len, line_num);
        }
#endif  /* RLOG_LINE_ENABLE */

        log_len += rlog_strcpy(log_len, rlog_buff + log_len, "]");
    }
#endif /* (RLOG_DIRECTORY_ENABLE == 1) || (RLOG_FUNCTION_ENABLE == 1) || (RLOG_LINE_ENABLE == 1) */

    // add level/tag to log
    if(rlog_level_fmt_get(level, RLOG_FMT_LVL | RLOG_FMT_TAG))
    {
        if(rlog_level_fmt_get(level, RLOG_FMT_LVL))
            log_len += rlog_strcpy(log_len, rlog_buff + log_len, level_to_info[level]);
        
        if(rlog_level_fmt_get(level, RLOG_FMT_TAG))
            log_len += rlog_strcpy(log_len, rlog_buff + log_len, tag);

        log_len += rlog_strcpy(log_len, rlog_buff + log_len, ": ");
    }

    va_start(args, fmt);
    fmt_len = rlog_vsnprintf(rlog_buff + log_len, RLOG_LINE_BUFF_LEN - log_len, fmt, args);

    if(((log_len + fmt_len) <= RLOG_LINE_BUFF_LEN) && (fmt_len > -1))
        log_len += fmt_len;
    else
        log_len = RLOG_LINE_BUFF_LEN;
    va_end(args);
    
#if RLOG_COLOUR_ENABLE
    if((RLOG_COLOUR_END_SIZE + log_len + newline_len) > RLOG_LINE_BUFF_LEN)
    {
        log_len = RLOG_LINE_BUFF_LEN;
        log_len -= RLOG_COLOUR_END_SIZE;
#else
    if((log_len + newline_len) > RLOG_LINE_BUFF_LEN)
    {
        log_len = RLOG_LINE_BUFF_LEN;
#endif /* RLOG_COLOUR_ENABLE */

        log_len -= newline_len;
    }

#if RLOG_COLOUR_ENABLE
    if(rlog.color_en)
        log_len += rlog_strcpy(log_len, rlog_buff + log_len, RLOG_COLOUR_END);
#endif /* RLOG_COLOUR_ENABLE */

    log_len += rlog_strcpy(log_len, rlog_buff + log_len, RLOG_NEWLINE_SIGN);

    rlog_output(rlog_buff, log_len);

    if(rlog_output_hook)
        rlog_output_hook(rlog_buff, log_len);

    rlog_unlock();
}

void rlog_hexdump_print(const char *title, uint8_t *buff, uint16_t buff_len)
{
#define __is_print(ch) ((unsigned int)((ch) - ' ') < 127u - ' ')
    unsigned char *buf = (unsigned char *)buff;
    int log_len = 0, fmt_len = 0;
    int i, j;
    
    if(!rlog.output_en)
        return;

    rlog_lock();    

    fmt_len = snprintf(rlog_buff, RLOG_LINE_BUFF_LEN, "RLOG HEXDUMP--Title: %s, len: %d\r\n", title, buff_len);
    if((fmt_len > -1) && (fmt_len < RLOG_LINE_BUFF_LEN))
        log_len += fmt_len;

    for (i = 0; i < buff_len; i += 16)
    {
        fmt_len = rlog_snprintf(rlog_buff + log_len, (RLOG_LINE_BUFF_LEN - log_len), "%08X: ", i);
        if((fmt_len > -1) && ((log_len + fmt_len) < RLOG_LINE_BUFF_LEN))
            log_len += fmt_len;

        for (j = 0; j < 16; j++)
        {
            if (i + j < buff_len)
            {
                fmt_len = rlog_snprintf(rlog_buff + log_len, (RLOG_LINE_BUFF_LEN - log_len), "%02X ", buf[i + j]);
                if((fmt_len > -1) && ((log_len + fmt_len) < RLOG_LINE_BUFF_LEN))
                    log_len += fmt_len;
            }
            else
                log_len += rlog_strcpy(log_len, rlog_buff + log_len, "   ");
        }        
        log_len += rlog_strcpy(log_len, rlog_buff + log_len, " ");
        for (j = 0; j < 16; j++)
        {
            if (i + j < buff_len)
            {
                fmt_len = rlog_snprintf(rlog_buff + log_len, (RLOG_LINE_BUFF_LEN - log_len), "%c", __is_print(buf[i + j]) ? buf[i + j] : '.');
                if((fmt_len > -1) && ((log_len + fmt_len) < RLOG_LINE_BUFF_LEN))
                    log_len += fmt_len;
            }
        }
        log_len += rlog_strcpy(log_len, rlog_buff + log_len, "\n");
    }

    rlog_output(rlog_buff, log_len);

    if(rlog_output_hook)
        rlog_output_hook(rlog_buff, log_len);

    rlog_unlock();
}

static uint8_t rlog_checksum(const uint8_t *data, uint8_t len)
{
    uint8_t index = 0, checksum = 0;
    for (; index < len; index++) {
        checksum += data[index];
    }
    return checksum;
}

void rlog_osc_print(rlog_osc_data *data)
{
    int index = 0;
    rlog_osc_packet packet = {0};

    packet.head = 'R';
    for(; index < RLOG_OSC_CH_MAX; index++)
    {
        packet.data.ch[index] = data->ch[index];
    }
    packet.checksum = rlog_checksum((uint8_t *)packet.data.ch, RLOG_OSC_CH_MAX * sizeof(float));
    packet.tail = 'C';
    rlog_osc_output((const char *)&packet, sizeof(rlog_osc_packet));
}

rlog_err_t rlog_init(void)
{
    rlog_adapter_init();

    /* Default filtered log level */
    rlog_level_filter_set(RLOG_LVL_VERBOSE);

    RLOG_PRINT("RLog Version: v%d.%d.%d\r\n", RLOG_VERSION_MAJOR, RLOG_VERSION_MINOR, RLOG_VERSION_PATCH);
    RLOG_PRINT("RLog Author: RiceChen\r\n"); 
    RLOG_PRINT("RLog Contact: https://gitee.com/RiceChen0/rlog\r\n");

    return RLOG_EOK;
}

void rlog_deinit(void)
{
    rlog_adapter_deinit();
}
